package com.kun.auth.authmanager;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.jwt.JWT;
import com.kun.auth.properties.WhiteProperties;
import com.kun.constant.SecurityConstant;
import com.kun.util.BuildKeyUtil;
import jakarta.annotation.Resource;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 自定义鉴权处理器
 *
 * @author kun.li
 */
@Slf4j
@Component
public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {


    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private WhiteProperties whiteProperties;

    private final PathMatcher pathMatcher = new AntPathMatcher();

    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext authorizationContext) {
        ServerWebExchange exchange = authorizationContext.getExchange();
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        String methodType = request.getMethod().name();
        // 对应跨域的预检请求直接放行
        if (request.getMethod() == HttpMethod.OPTIONS) {
            return Mono.just(new AuthorizationDecision(true));
        }
        // 白名单直接放行 白名单配置在配置中心
        List<String> ignores = whiteProperties.getIgnores();
        if (whiteMatch(ignores, methodType, path)) {
            return Mono.just(new AuthorizationDecision(true));
        }
        // token为空拒绝访问
        String token = request.getHeaders().getFirst(SecurityConstant.AUTHORIZATION);
        if (!StringUtils.hasText(token)) {
            return Mono.just(new AuthorizationDecision(false));
        }
        // 从token中解析出用户名 sub 证明这个jwt属于谁
        JSONObject payloads = JWT.of(token.replaceFirst(SecurityConstant.BEARER, "")).getPayloads();
        JSONObject userInfo = payloads.getJSONObject(SecurityConstant.USERINFO);
        String tenantId = payloads.getStr(SecurityConstant.TENANT_ID);
        String hasAdmin = payloads.getStr(SecurityConstant.HAS_ADMIN);
        String username = userInfo.getStr(SecurityConstant.USERNAME);
        Map<Object, Object> permRolesRules = redisTemplate.opsForHash().entries(BuildKeyUtil.buildTenantKey(username,tenantId));
        Set<String> hasPermissionRoles = new HashSet<>();
        for (Map.Entry<Object, Object> objectObjectEntry : permRolesRules.entrySet()) {
            String url = (String) objectObjectEntry.getKey();
            String role = (String) objectObjectEntry.getValue();
            if (pathMatcher.match(url, methodType + path)) {
                hasPermissionRoles.addAll(Arrays.asList(role.split(",")));
                break;
            }
        }
        // 封装请求头
        List<Authorities> authorities = userInfo.getBeanList(SecurityConstant.AUTHORITIES, Authorities.class);
        String roles = authorities.stream().map(Authorities::getRole)
                .collect(Collectors.joining(","));
        String userId = payloads.getStr(SecurityConstant.USERID);
        // 开始 将信息放入请求头,request.mutate() 来加入请求头信息
        // request.getHeader() 默认时可读的
        ServerHttpRequest.Builder mutate = request.mutate();
        mutate.header(SecurityConstant.USERNAME, username);
        mutate.header(SecurityConstant.USERID, userId);
        mutate.header(SecurityConstant.ROLES, roles);
        mutate.header(SecurityConstant.TENANT_ID, tenantId);
        mutate.header(SecurityConstant.HAS_ADMIN,hasAdmin);
        ServerHttpRequest httpRequest = mutate.build();
        exchange.mutate().request(httpRequest).build();
        // 结束

        return authentication.filter(Authentication::isAuthenticated)
                .flatMapIterable(Authentication::getAuthorities)
                .map(GrantedAuthority::getAuthority)
                .any(role -> {
                    log.info("用户权限（角色） : {}", role); // admin ,test
                    return hasPermissionRoles.contains(role);
                })
                .map(AuthorizationDecision::new)
                .defaultIfEmpty(new AuthorizationDecision(false));
    }

    public boolean whiteMatch(List<String> ignores, String methodType, String path) {
        if (CollUtil.isNotEmpty(ignores)) {
            if (ignores.contains(path) || ignores.contains(methodType + path)) {
                return true;
            }
            return ignores.stream().anyMatch(ignore -> pathMatcher.match(ignore, path));
        }
        return false;
    }

    @Data
    static class Authorities {

        private String role;

    }


}
